<?php

Class PdoDB
{
	 /**
     * @var array Part of SQL, maping the function name and the operator to build SQL Part.
     * <pre>call function like this: 
     *      $user->order('id desc', 'name asc')->limit(2,1);
     *  can explain to SQL:
     *      ORDER BY id desc, name asc limit 2,1</pre>
     */

     private static $db;
    public static $sqlParts = array(
        'select' => 'SELECT',
        'from' => 'FROM',
        'table' => 'FROM',
        'set' => 'SET',
        'where' => 'WHERE',
        'group' => 'GROUP BY','groupby' => 'GROUP BY',
        'having' => 'HAVING',
        'order' => 'ORDER BY','orderby' => 'ORDER BY',
        'limit' => 'limit',
        'top' => 'TOP',
        'andwhere'=>''
    );

    protected $sqlExpressions = array();

    public $table;

    /**
     * @var array Static property to stored the default Sql Expressions values.
     */
    public static $defaultSqlExpressions = array('expressions' => array(), 'wrap' => false,
        'select'=>null, 'insert'=>null, 'update'=>null, 'set' => null, 'delete'=>'DELETE ', 'join' => null,
        'from'=>null, 'values' => null, 'where'=>null, 'having'=>null, 'limit'=>null, 'order'=>null, 'group' => null);


    public $params = array();

        /**
     * function to find one record and assign in to current object.
     * @param int $id If call this function using this param, will find record by using this id. If not set, just find the first record in database.
     * @return bool|ActiveRecord if find record, assign in to current object and return it, other wise return "false".
     */
    public function find() {
        return self::query($this->limit(1)->_buildSql(array('select', 'from', 'join', 'where', 'group', 'having', 'order', 'limit')), $this->params);
    }

    /**
     * function to find all records in database.
     * @return array return array of ActiveRecord
     */
    public function findAll() {
        return self::query($this->_buildSql(array('select', 'from', 'table','join', 'where', 'group', 'having', 'order', 'limit')), $this->params);
    }
    
    public function query($sql,$params)
    {
    	 return $this->db->query($sql,$params);
    }

    public function getdb()
    {
    	if(empty(self::$db)){

    		self::$db =PDOConnection::getInstance();
    	 }
    	 return self::$db;
    }

    /**
     * helper function to add condition into WHERE. 
     * create the SQL Expressions.
     * @param string $field The field name, the source of Expressions
     * @param string $operator 
     * @param mixed $value the target of the Expressions
     * @param string $op the operator to concat this Expressions into WHERE or SET statment.
     * @param string $name The Expression will contact to.
     */
    public function andwhere($args,$params=array()) {
        if(!empty($this->where)) {
            $arr[] = $this->where->target;
            $arr[] = $args;
            $args = implode('AND',$ar );
        } 
        $this->where = new Expressions(array('operator'=>self::$sqlParts['where'], 'target' => $args));
        if(isset($args[1])) {
          $this->params = array_merge($this->params,$params);
        }
        return $this;
    }
   

     /**
     * helper function to build SQL with sql parts.
     * @param string $n The SQL part will be build.
     * @param int $i The index of $n in $sqls array.
     * @param ActiveRecord $o The refrence to $this
     * @return string 
     */
    private function _buildSqlCallback(&$n, $i, $o){
        if ('select' === $n && null == $o->$n) $n = strtoupper($n). ' '.$o->table.'.*';
        elseif (('update' === $n||'from' === $n) && null == $o->$n) $n = strtoupper($n).' '. $o->table;
        elseif ('delete' === $n) $n = strtoupper($n). ' ';
        else $n = (null !== $o->$n) ? $o->$n. ' ' : '';


    }
    /**
     * helper function to build SQL with sql parts.
     * @param array $sqls The SQL part will be build.
     * @return string 
     */
    protected function _buildSql($sqls = array()) {

        array_walk($sqls, array($this, '_buildSqlCallback'), $this);
        //this code to debug info.
        //echo 'SQL: ', implode(' ', $sqls), "\n", "PARAMS: ", implode(', ', $this->params), "\n";
        return implode(' ', $sqls);
    }



	public function __call($name, $args) {
        if (method_exists($this,$name)){
            $callback = array($this,$name);
             return call_user_func_array($callback, $args);
        }else if (in_array($name= str_replace('by', '', $name), array_keys(self::$sqlParts))) {
            $this->$name = new Expressions(array('operator'=>self::$sqlParts[$name], 'target' => implode(', ', $args)));
        } else throw new Exception("Method $name not exist.");
        return $this;
    }

  

    private function table($args)
    {

       $this->table ='{{'.$args.'}}';
       
       return $this;
    }

    private function params($params)
    {

       $this->params =array_merge($this->params,$params);
       
       return $this;
    }

    public static function __callStatic($name, $args)
    {
      $instance = new self(); 	
      if (is_callable($callback = array($instance,$name)))
            return call_user_func_array($callback, $args);
    }

    public function __set($name,$val)
    {
      if(method_exists($this,'set'.ucfirst($name))) {
        return call_user_func_array( array($this,'set'.ucfirst($name)), array($name,$val));
      }
    }

    public function __get($name)
    {
      if(method_exists($this,'get'.ucfirst($name))) {
        return call_user_func_array( array($this,'get'.ucfirst($name)), array($name));
      }
    }

    public function setParams($name,$val)
    {
      $this->$name = $val;
    }

    public function setWhere($name,$val)
    {
        $this->where = $val;
    }

    public function setandwhere($name,$val)
    {
        $this->where = $val;
    }

    public function getParams($name)
    {
      return $this->$name; 
    }


    public function where($args,$params)
    {

       $this->where = new Expressions(array('operator'=>self::$sqlParts['where'], 'target' => $args));
       if(isset($params)) {
         $this->params = array_merge($this->params,$params);
       }
       return $this;
    }

}

/**
 * base class to stord attributes in one array.
 */
abstract class Base {
    /**
     * @var array Stored the attributes of the current object
     */
    public $data = array();
    public function __construct($config = array()) {
        foreach($config as $key => $val) $this->$key = $val;
    }
    public function __set($var, $val) {
        $this->data[$var] = $val;
    }
    public function & __get($var) {
        $result = isset($this->data[$var]) ? $this->data[$var] : null;
        return $result;
    }
}
/**
 * Class Expressions, part of SQL.
 * Every SQL can be split into multiple expressions.
 * Each expression contains three parts: 
 * @property string|Expressions $source of this expression, (option)
 * @property string $operator (required)
 * @property string|Expressions $target of this expression (required)
 * Just implement one function __toString.
 */
class Expressions extends Base {
    public function __toString() {
        return $this->source. ' '. $this->operator. ' '. $this->target;
    }
}
/**
 * Class WrapExpressions 
 */
class WrapExpressions extends Expressions {
    public function __toString() {
        return ($this->start ? $this->start: '('). implode(($this->delimiter ? $this->delimiter: ','), $this->target). ($this->end?$this->end:')');
    }
}